Skip to content

sweepbatcher: fixes for presigned mode #952

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

starius
Copy link
Collaborator

@starius starius commented Jun 12, 2025

Fixed TODO left from #891 : re-add sweeps to new batches only after fully confirmed.
In case of a reorg sweeps should not go to another batch but stay in the current batch until it is fully confirmed. Only after that the remaining sweeps are re-added to another batch. Field sweep.completed is now set to true only for fully-confirmed sweeps.

Fixed OnChainFeePortion values. There were two mistakes. In case of a swap with multiple sweeps only the fee of the first sweep of a swap was accounted. Another issue is that rounding diff (the remainder) was attributed to all the sweeps rather than to the first (primary) sweep of the batch. The sweep to attribute the remainder was chosen by comparing SignatureScript which is
always empty. New approach is to find the primary sweep and to compare its outpoint directly.

Pull Request Checklist

  • Update release_notes.md if your PR contains major features, breaking changes or bugfixes

@starius starius marked this pull request as ready for review June 12, 2025 19:15
@starius starius requested review from bhandras, sputn1ck and hieblmi June 13, 2025 03:17
Copy link
Collaborator

@hieblmi hieblmi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice work @starius, I did a first pass of the PR.

@lightninglabs-deploy
Copy link

@bhandras: review reminder
@sputn1ck: review reminder
@starius, remember to re-request review from reviewers when ready

@starius starius force-pushed the presigned-fixes2 branch from 239e288 to 70dd1a7 Compare June 24, 2025 15:58
@starius starius requested a review from hieblmi June 24, 2025 16:08
@bhandras bhandras removed the request for review from sputn1ck June 25, 2025 05:45
Copy link
Member

@bhandras bhandras left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 🎉

go func() {
// Make sure this context doesn't expire so we
// successfully notify the caller.
ctx := context.WithoutCancel(ctx)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: alternatively we could just create a new context.Background() and use that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer context.WithoutCancel, because it inherits key-values of the parent context. This is used for new logging.

confirmedSet[txIn.PreviousOutPoint] = struct{}{}
}

// As a previous version of the batch transaction may get confirmed,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think this comment is a bit misleading as we don't really collect the non-confirmed sweeps here to feed them back to the batcher, we just notify those that confirmed and then start the monitoring for 3 conf after which we will do the collection and re-feeding.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment was outdated. I updated it:

// As a previous version of the batch transaction may get confirmed,
// which does not contain the latest sweeps, we need to detect which
// sweeps are in the transaction to correctly calculate fee portions
// and notify proper sweeps.

@@ -604,8 +605,9 @@ func CheckSignedTx(unsignedTx, signedTx *wire.MsgTx, inputAmt btcutil.Amount,
unsignedOut := unsignedTx.TxOut[0]
signedOut := signedTx.TxOut[0]
if !bytes.Equal(unsignedOut.PkScript, signedOut.PkScript) {
return fmt.Errorf("mismatch of output pkScript: %v, %v",
unsignedOut.PkScript, signedOut.PkScript)
return fmt.Errorf("mismatch of output pkScript: %s, %s",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can just use %x

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

infof("PresignSweepsGroup: nextBlockFeeRate is %v, inputs: %v, "+
"destAddress: %v, destPkscript: %v sweepTimeout: %d",
nextBlockFeeRate, inputs, destAddress,
hex.EncodeToString(destPkscript), sweepTimeout)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can just use %x

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

starius added 15 commits June 25, 2025 22:53
This is needed to have multiple spending registrations running and to send
a notification to a specific spending registration.
In case of a reorg sweeps should not go to another batch but stay in the current
batch until it is fully confirmed. Only after that the remaining sweeps are
re-added to another batch.

Field sweep.completed is now set to true only for fully-confirmed sweeps.

In handleConf we now use batch.persist() (i.e. store.UpdateSweepBatch) instead
of ConfirmBatch, because we set not only Confirmed flag, but also batchTxid.
There were two mistakes.

In case of a swap with multiple sweeps only the fee of the first sweep
of a swap was accounted.

Rounding diff (the remainder) was attributed to all the sweeps rather than to
the first (primary) sweep of the batch. The sweep to attribute the remainder
was chosen by comparing SignatureScript which is always empty. New approach is
to find the primary sweep and to compare its outpoint directly.
This is needed because sweepbatcher can use this channel in multiple
select statements to unblock itself if the caller cancels.
It doesn't need loopdb, so remove that code.
Make sure that broadcasted tx has feeRate >= minRelayFee.
Make sure that feeRate of broadcasted tx doesn't decrease.
Method Presign is not as reliable as SignTx, because it checks transaction by
txid and can miss for example if LockTime is different. SignTx can do everything
Presign was used for.
For presigned possible remaining groups, the destination address of the current
batch was used instead of the destination address of an expected future batch.

TODO: reproduce in unit test "purged". For this, each swap should have a
separate destination address.
@starius starius force-pushed the presigned-fixes2 branch from 59da056 to 1301904 Compare June 26, 2025 02:05
Copy link
Collaborator

@hieblmi hieblmi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work @starius! The detailed commit messages make it very pleasant to review.


// We are no longer able to accept new sweeps, so we mark the batch as
// closed and persist on storage.
b.state = Closed
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking, this couldn't this result in races because of b.scheduleNextCall(), right? E.g. in

if b.state != Open {

@@ -236,7 +236,7 @@ type dbSweep struct {
// Amount is the amount of the sweep.
Amount btcutil.Amount

// Completed indicates whether this sweep is completed.
// Completed indicates whether this sweep is fully-confirmed.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: to clarify maybe say "... this sweep is considered reorg-safely confirmed."

@@ -274,7 +274,7 @@ type addSweepsRequest struct {
notifier *SpendNotifier

// completed is set if the sweep is spent and the spending transaction
// is confirmed.
// is fully confirmed.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: to clarify maybe say "... the spending transaction is considered reorg-safely confirmed."

@@ -1147,7 +1147,7 @@ func (s *loopOutSwap) waitForHtlcSpendConfirmedV2(globalCtx context.Context,
quitChan := make(chan bool, 1)

defer func() {
quitChan <- true
close(quitChan)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so before this change, only one of multiple selects would have ended?

type FeeRateProvider func(ctx context.Context,
swapHash lntypes.Hash) (chainfee.SatPerKWeight, error)
type FeeRateProvider func(ctx context.Context, swapHash lntypes.Hash,
utxo wire.OutPoint) (chainfee.SatPerKWeight, error)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this needed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants